package com.imooc.ecommerce.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.imooc.ecommerce.account.AddressInfo;
import com.imooc.ecommerce.account.BalanceInfo;
import com.imooc.ecommerce.common.TableId;
import com.imooc.ecommerce.entity.EcommerceOrder;
import com.imooc.ecommerce.feign.AddressClient;
import com.imooc.ecommerce.feign.NotSecuredBalanceClient;
import com.imooc.ecommerce.feign.NotSecuredGoodsClient;
import com.imooc.ecommerce.feign.SecuredGoodsClient;
import com.imooc.ecommerce.filter.AccessContext;
import com.imooc.ecommerce.goods.DeductGoodsInventory;
import com.imooc.ecommerce.goods.SimpleGoodsInfo;
import com.imooc.ecommerce.mapper.EcommerceOrderMapper;
import com.imooc.ecommerce.order.LogisticsMessage;
import com.imooc.ecommerce.order.OrderInfo;
import com.imooc.ecommerce.service.IOrderService;
import com.imooc.ecommerce.source.LogisticsSource;
import com.imooc.ecommerce.vo.SingleOrderItem;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 订单相关服务接口实现
 *
 * @author zhangshao
 * @since 2023/12/14 21:32
 */
@Slf4j
@Service
@EnableBinding(LogisticsSource.class)
public class OrderServiceImpl implements IOrderService {

    @Autowired
    private EcommerceOrderMapper ecommerceOrderMapper;

    @Autowired
    private AddressClient addressClient;

    @Autowired
    private SecuredGoodsClient securedGoodsClient;

    @Autowired
    private NotSecuredGoodsClient notSecuredGoodsClient;

    @Autowired
    private NotSecuredBalanceClient notSecuredBalanceClient;

    /**
     * Spring Cloud Stream 的发射器
     */
    @Autowired
    private LogisticsSource logisticsSource;


    /**
     * 创建订单: 这里会涉及到分布式事务
     * 创建订单会涉及到多个步骤和校验,当不满足情况时直接抛出异常;
     * 1. 校验请求对象是否合法
     * 2. 创建订单
     * 3. 扣减商品库存
     * 4. 扣减用户余额
     * 5. 发送订单物流信息 SpringCloud Stream +kafka
     *
     * @param orderInfo
     * @return
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public TableId createOrder(OrderInfo orderInfo) {

        // 获取地址信息
        AddressInfo addressInfo = addressClient.getAddressInfoByTableId(
                new TableId(Collections.singletonList(
                        new TableId.Id(orderInfo.getUserAddress())))).getData();
        //1. 校验请求对象是否合法(商品信息不需要校验,扣减库存会做校验)
        if (CollectionUtils.isEmpty(addressInfo.getAddressItems())) {
            throw new RuntimeException("user address is not exist: " + orderInfo.getUserAddress());
        }
        EcommerceOrder newOrder = new EcommerceOrder(AccessContext.getLoginUserInfo().getId(),
                orderInfo.getUserAddress(), JSON.toJSONString(orderInfo.getOrderItems())
        );
        //2. 创建订单
        ecommerceOrderMapper.insert(newOrder);

        log.info("create order success: [{}] ,[{}]", AccessContext.getLoginUserInfo().getId(), newOrder.getId());
        // 3. 扣减商品库存
        if (
                !notSecuredGoodsClient.deductGoodsInventory(
                        orderInfo.getOrderItems()
                                .stream()
                                .map(OrderInfo.OrderItem::toDeductGoodsInventory)
                                .collect(Collectors.toList())).getData()
        ) {
            throw new RuntimeException("deduct goods inventory failure");
        }
        // 4. 扣减用户账户余额
        // 4.1 获取商品信息,计算总价格
        List<SimpleGoodsInfo> goodsInfos = notSecuredGoodsClient.getSimpleGoodsInfoByTableId(
                new TableId(
                        orderInfo.getOrderItems()
                                .stream()
                                .map(o -> new TableId.Id(o.getGoodsId()))
                                .collect(Collectors.toList())
                )
        ).getData();
        Map<Long, SimpleGoodsInfo> goodsId2GoodsInfo = goodsInfos.stream().
                collect(Collectors.toMap(SimpleGoodsInfo::getId, Function.identity()));
        long balance = 0;
        for (OrderInfo.OrderItem orderItem : orderInfo.getOrderItems()) {
            balance += goodsId2GoodsInfo.get(orderItem.getGoodsId()).getPrice() * orderItem.getCount();

        }
        assert balance > 0;
        // 4.2 填写总价格,扣减账户余额
        BalanceInfo balanceInfo = notSecuredBalanceClient.deductBalance(
                new BalanceInfo(AccessContext.getLoginUserInfo().getId(), balance)
        ).getData();
        if (null == balanceInfo) {
            throw new RuntimeException("deduct user balance failure");
        }
        log.info("deduct user balance :[{}],[{}]",newOrder.getId(),JSON.toJSONString(balanceInfo));

        // 5.发送订单物流消息 SpringCloud stream + kafka
        LogisticsMessage logisticsMessage = new LogisticsMessage(
                AccessContext.getLoginUserInfo().getId(),
                newOrder.getId(),
                orderInfo.getUserAddress(),
                // 没有备注信息
                null
        );
        if(!logisticsSource.logisticsOutput().send(
                MessageBuilder.withPayload(JSON.toJSONString(logisticsMessage)).build()
        )){
            throw new RuntimeException("send logistics message failure");
        }
        log.info("send create order message to kafka with stream :[{}]",JSON.toJSONString(logisticsMessage));

        //返回订单id
        return new TableId(Collections.singletonList(new TableId.Id(newOrder.getId())));
    }

    @Override
    public IPage<SingleOrderItem> getSimpleOrderDetailByPage(int pageNum) {
        if(pageNum <=0){
            // 默认是第一页
            pageNum =1;
        }
        // 这里分页规则是: 1页10条数据,按照id倒序排列
        Page<EcommerceOrder> page = new Page<>(pageNum, 10);

        Page<SingleOrderItem> resPage = new Page<>(pageNum, 10);

        LambdaQueryWrapper<EcommerceOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(EcommerceOrder::getUserId,AccessContext.getLoginUserInfo().getId());
        queryWrapper.orderByDesc(EcommerceOrder::getId);
        List<EcommerceOrder> ecommerceOrders = ecommerceOrderMapper.selectPage(page, queryWrapper).getRecords();
        // 如果为空,返回空数组
        if(CollectionUtils.isEmpty(ecommerceOrders)){

            resPage.setRecords(Lists.newArrayList());
            return resPage;
        }
        // 获取当前订单中所有的goodsId
        Set<Long> goodsInOrders = new HashSet<>();
        ecommerceOrders.forEach( o->{
            List<DeductGoodsInventory> goodsAndCount = JSON.parseArray(o.getOrderDetail(), DeductGoodsInventory.class);
            goodsInOrders.addAll(goodsAndCount.stream().map(DeductGoodsInventory::getGoodsId).collect(Collectors.toSet()));
        });
        assert CollectionUtil.isNotEmpty(goodsInOrders);

        List<SimpleGoodsInfo> goodsInfos = securedGoodsClient.getSimpleGoodsInfoByTableId(
                new TableId(goodsInOrders.stream().map(TableId.Id::new).collect(Collectors.toList()))
        ).getData();

        AddressInfo addressInfo = addressClient.getAddressInfoByTableId(
                new TableId(ecommerceOrders.stream().map( o->new TableId.Id(o.getAddressId())).distinct().collect(Collectors.toList()))
        ).getData();

        // 组装订单中的商品, 地址信息 -->订单信息
        resPage.setRecords(assembleSimpleOrderItem(
                ecommerceOrders,goodsInfos,addressInfo
        ));

        return resPage;
    }

    /**
     * 组装订单详情
     * @return
     */
    private List<SingleOrderItem> assembleSimpleOrderItem(List<EcommerceOrder> orders,List<SimpleGoodsInfo> goodsInfos,AddressInfo addressInfo){
        // goodsId --> simpleGoodsInfo
        Map<Long,SimpleGoodsInfo> is2GoodsInfo = goodsInfos.stream().collect(Collectors.toMap(SimpleGoodsInfo::getId,Function.identity()));
        //addressId --> addressInfo.AddressItem
        Map<Long, AddressInfo.AddressItem> id2AddressItem = addressInfo.getAddressItems().stream().collect(Collectors.toMap(AddressInfo.AddressItem::getId,Function.identity()));
        List<SingleOrderItem> result = new ArrayList<>(orders.size());
        orders.forEach(o ->{
            SingleOrderItem orderItem = new SingleOrderItem();
            orderItem.setId(o.getId());
            orderItem.setUserAddress(id2AddressItem.getOrDefault(o.getAddressId(),new AddressInfo.AddressItem(-1L)).toUserAddress());
            orderItem.setSingleOrderGoodsItems(buildOrderGoodsItem(o,is2GoodsInfo));
            result.add(orderItem);
        });

        return result;
    }

    /**
     * 构造订单中的商品信息
     * @param order
     * @param id2GoodsInfo
     * @return
     */
    private List<SingleOrderItem.SingleOrderGoodsItem> buildOrderGoodsItem(EcommerceOrder order,Map<Long, SimpleGoodsInfo> id2GoodsInfo){
        List<SingleOrderItem.SingleOrderGoodsItem> goodsItems = new ArrayList<>();
        List<DeductGoodsInventory> goodsAndCount = JSON.parseArray(order.getOrderDetail(),DeductGoodsInventory.class);

        goodsAndCount.forEach(gc ->{
            SingleOrderItem.SingleOrderGoodsItem goodsItem = new SingleOrderItem.SingleOrderGoodsItem();
            goodsItem.setCount(gc.getCount());
            goodsItem.setSimpleGoodsInfo(id2GoodsInfo.getOrDefault(gc.getGoodsId(),new SimpleGoodsInfo(-1L)));
            goodsItems.add(goodsItem);

        });
        return goodsItems;

    }

}
